/**
 * @file lv_appmenu.c
 *
 */

/*********************
 *      INCLUDES
 *********************/
#include "lv_appmenu.h"

/*********************
 *      DEFINES
 *********************/

/**********************
 *      TYPEDEFS
 **********************/

/**********************
 *  STATIC PROTOTYPES
 **********************/
static uint16_t get_real_appicon_id(lv_appmenu_t *appmenu, uint16_t appicon_id);
static void appicon_event_handler(lv_event_t * e);
static lv_obj_t * change_focus(lv_appmenu_t *appmenu, uint16_t hfocus, uint16_t vfocus);
static void key_pro(lv_appmenu_t *appmenu, uint32_t key);
static void create_cell_cb(lv_obj_t * matrix, uint16_t hpos, uint16_t vpos);
static void event_cell_cb(lv_obj_t *matrix, lv_event_code_t event_code, int16_t hpos, int16_t vpos);
static void delete_cell_cb(lv_obj_t * matrix, uint16_t hpos, uint16_t vpos);

/**********************
 *  STATIC VARIABLES
 **********************/

/**********************
 *      MACROS
 **********************/

/**********************
 *   GLOBAL FUNCTIONS
 **********************/

void lv_appmenu_create(lv_appmenu_t *appmenu, lv_obj_t * parent, const lv_appmenu_info_t *info, const uint16_t *appicon_lut)
{
    appmenu->appicon_lut = appicon_lut;
    appmenu->info = info;
    /*create bg image*/
    lv_obj_t * obj_bg = lv_img_create(parent);
    lv_img_set_src(obj_bg, info->bg_image_path);
    lv_obj_clear_flag(obj_bg, LV_OBJ_FLAG_SCROLLABLE);
    lv_obj_set_size(obj_bg, LV_PCT(100), LV_PCT(100));
    //lv_image_set_inner_align(obj_bg, LV_IMAGE_ALIGN_STRETCH);/*adaptive scaling*/
    lv_image_set_inner_align(obj_bg, LV_IMAGE_ALIGN_CENTER);
    lv_obj_center(obj_bg);
    appmenu->obj_bg = obj_bg;
    /*create appicon matrix*/
    struct _lv_obj_matrix_info_t matrix_info;
    matrix_info.create_cell_cb = create_cell_cb;
    matrix_info.delete_cell_cb = delete_cell_cb;
    matrix_info.event_cell_cb = event_cell_cb;
    lv_memcpy(&matrix_info.margin, &info->matrix_margin, sizeof(lv_obj_matrix_margin_t));
    lv_memcpy(&matrix_info.cellsize, &info->matrix_cellsize, sizeof(lv_obj_matrix_cell_size_t));
    matrix_info.hcount = 0; matrix_info.vcount = 0;
    if(appicon_lut != NULL) matrix_info.ccount = *appicon_lut;
    else matrix_info.ccount = info->appicon_count;
    matrix_info.align = info->matrix_align;
    lv_obj_t * obj_matrix = lv_obj_matrix_create(obj_bg);
    lv_obj_matrix_set_info(obj_matrix, &matrix_info);
    lv_obj_set_user_data(obj_matrix, appmenu);
    appmenu->obj_matrix = obj_matrix;
    lv_obj_set_style_bg_opa(obj_matrix, 0, 0);
    //lv_obj_add_flag(obj_matrix, LV_OBJ_FLAG_SCROLLABLE);
    lv_obj_matrix_update(obj_matrix);
    change_focus(appmenu, 0, 0);
}

void lv_appmenu_delete(lv_appmenu_t *appmenu)
{
    lv_obj_matrix_del_preprocessing(appmenu->obj_matrix);
    lv_obj_delete(appmenu->obj_bg);
}

void lv_appmenu_set_focus(lv_appmenu_t *appmenu, uint16_t hfocus, uint16_t vfocus)
{
    change_focus(appmenu, hfocus, vfocus);
}

void lv_appmenu_rebuild_bg(lv_appmenu_t *appmenu)
{
    lv_obj_t * obj_bg = appmenu->obj_bg;
    lv_img_set_src(obj_bg, appmenu->info->bg_image_path);
    lv_obj_invalidate(obj_bg);
}

void lv_appmenu_rebuild_appicon(lv_appmenu_t *appmenu)
{
    //lv_obj_matrix_set_ccount(appmenu->obj_matrix);
    lv_obj_matrix_update(appmenu->obj_matrix);
}

uint16_t lv_appmenu_get_hfocus(const lv_appmenu_t *appmenu)
{
    return appmenu->hfocus;
}

uint16_t lv_appmenu_get_vfocus(const lv_appmenu_t *appmenu)
{
    return appmenu->vfocus;
}

/**********************
 *   STATIC FUNCTIONS
 **********************/

static uint16_t get_real_appicon_id(lv_appmenu_t *appmenu, uint16_t appicon_id)
{
    const uint16_t *appicon_lut = appmenu->appicon_lut;
    if(appicon_lut == NULL) return appicon_id;
    uint16_t appicon_count =  *appicon_lut++;
    if(appicon_id >= appicon_count) return appicon_id;
    return appicon_lut[appicon_id];
}

static void appicon_event_handler(lv_event_t * e)
{
    lv_event_code_t code = lv_event_get_code(e);
    if((code != LV_EVENT_CLICKED) && (code != LV_EVENT_KEY) && (code != LV_EVENT_FOCUSED))return;

    lv_obj_t *appicon = lv_obj_get_parent(lv_event_get_target_obj(e));
    int32_t appicon_id = lv_obj_get_index(appicon);
    if(appicon_id < 0) return;
    lv_obj_t * matrix = lv_obj_get_parent(appicon);
    lv_appmenu_t *appmenu = (lv_appmenu_t *)lv_obj_get_user_data(matrix);
    if(code != LV_EVENT_KEY){
        uint16_t hcount = lv_obj_matrix_get_hcount(matrix);
        change_focus(appmenu, appicon_id % hcount,  appicon_id / hcount);
        lv_group_focus_obj(matrix);
        if(code == LV_EVENT_FOCUSED) return;
    }
    const struct _lv_appmenu_info_t *info = appmenu->info;
    if(info->enter_app_cb != NULL){
        appicon_id = get_real_appicon_id(appmenu, appicon_id);
        info->enter_app_cb(appmenu, appicon_id, (appmenu->info->appicon_info_ary + appicon_id)->id);
    }
}

static lv_obj_t * change_focus(lv_appmenu_t *appmenu, uint16_t hfocus, uint16_t vfocus)
{
    lv_obj_t * matrix = appmenu->obj_matrix;
    int16_t appicon_id = lv_obj_matrix_get_cell_id(matrix, hfocus, vfocus);
    if(appicon_id < 0){
        if(appmenu->info->cfg & LV_APPMENU_KEY_LOOP){
            hfocus = 0;
            vfocus = 0;
        }
        else{
            uint16_t last = lv_obj_matrix_get_valid_count(matrix) - 1;
            uint16_t vcount = lv_obj_matrix_get_vcount(matrix);
            hfocus = last / vcount;
            vfocus = last % vcount;
        }
    }
    lv_obj_t * appicon = lv_obj_matrix_get_cell_container(matrix, appmenu->hfocus, appmenu->vfocus);
    if(appicon != NULL) lv_appicon_remove_focused(appicon);
    appicon = lv_obj_matrix_get_cell_container(matrix, hfocus, vfocus);
    if(appicon != NULL){
       lv_appicon_add_focused(appicon);
       lv_obj_scroll_to_view(appicon, 1);
    }
    appmenu->hfocus = hfocus;
    appmenu->vfocus = vfocus;
    return appicon;
}

static void key_pro(lv_appmenu_t *appmenu, uint32_t key)
{
    lv_obj_t * matrix = appmenu->obj_matrix;
    uint16_t hfocus = appmenu->hfocus;
    uint16_t vfocus = appmenu->vfocus;
    uint16_t vcount = lv_obj_matrix_get_vcount(matrix);
    uint16_t hcount = lv_obj_matrix_get_hcount(matrix);
    uint8_t is_loop = appmenu->info->cfg & LV_APPMENU_KEY_LOOP;
    uint8_t is_line_loop = appmenu->info->cfg & LV_APPMENU_LINE_LOOP;
    if(key == LV_KEY_ENTER){
        lv_obj_t * appicon = lv_obj_matrix_get_cell_container(matrix, hfocus, vfocus);
        if(appicon != NULL) lv_obj_send_event(lv_obj_get_child(appicon, 0), LV_EVENT_KEY, &key);
        return;
    }

    if(key == LV_KEY_LEFT){
        if(hfocus) hfocus--;
        else if(is_line_loop) hfocus = hcount - 1;
        else{
            hfocus = hcount - 1;
            if(vfocus) vfocus--;
            else if(is_loop && vcount) vfocus = vcount - 1;
        }
    }
    else if(key == LV_KEY_RIGHT){
        hfocus++;
        if(hfocus >= hcount) {
            hfocus = 0;
            vfocus++;
            if(vfocus >= vcount){
                if(is_line_loop) vfocus = 0;
                else vfocus--;
            }
        }
    }
    else if(key == LV_KEY_UP){
        if(vfocus) vfocus--;
        else if(is_loop && vcount) vfocus = vcount - 1;
    }
    else if(key == LV_KEY_DOWN){
        vfocus++;
        if(vfocus >= vcount) {
            if(is_loop) vfocus = 0;
            else vfocus--;
        }
    }
    else if((key == LV_KEY_HOME) || (key == LV_KEY_END)){/*page up or page down*/
        if(appmenu->info->appicon_count < 0){ /*Horizontal growth*/
            uint16_t pagesize = lv_obj_get_width(matrix) / appmenu->info->matrix_cellsize.x;
            if(key == LV_KEY_HOME){
              if(hfocus >= pagesize) hfocus -= pagesize;
              else hfocus = 0;
            }
            else{
              hfocus += pagesize;
              if(hfocus >= hcount)  hfocus = hcount - 1;
            }
        }
        else{ /*Vertical growth*/
            uint16_t pagesize = lv_obj_get_height(matrix) / appmenu->info->matrix_cellsize.y;
            if(key == LV_KEY_HOME){
              if(vfocus >= pagesize) vfocus -= pagesize;
              else vfocus = 0;
            }
            else{
              vfocus += pagesize;
              if(vfocus >= vcount)  vfocus = vcount - 1;
            }
        }
    }
    change_focus(appmenu, hfocus, vfocus);
}

static void create_cell_cb(lv_obj_t * matrix, uint16_t hpos, uint16_t vpos)
{
    lv_obj_t * appicon = lv_obj_matrix_get_cell_container(matrix, hpos, vpos);
    lv_obj_set_style_bg_opa(appicon, 0, 0);
    if(appicon == NULL) return;
    uint16_t appicon_id = lv_obj_matrix_get_cell_id(matrix, hpos, vpos);
    if(appicon_id < 0) return;
    lv_appmenu_t *appmenu = (lv_appmenu_t *)lv_obj_get_user_data(matrix);
    appicon_id = get_real_appicon_id(appmenu, appicon_id);
    if(appicon_id >= LV_ABS(appmenu->info->appicon_count)) return;
    lv_appicon_create(appicon, appmenu->info->appicon_info_ary + appicon_id, &appmenu->info->appicon_cfg, appicon_event_handler);
}

static void event_cell_cb(lv_obj_t *matrix, lv_event_code_t event_code, int16_t hpos, int16_t vpos)
{
    lv_appmenu_t *appmenu = (lv_appmenu_t *)lv_obj_get_user_data(matrix);
    //if(event_code == LV_EVENT_SIZE_CHANGED) {
    //    if((hpos < 0) || (vpos < 0)) return;
    //    return;
    //}
    if(event_code == LV_EVENT_KEY) key_pro(appmenu, hpos);
}

static void delete_cell_cb(lv_obj_t * matrix, uint16_t hpos, uint16_t vpos)
{
    lv_obj_t * appicon = lv_obj_matrix_get_cell_container(matrix, hpos, vpos);
    if(appicon == NULL) return;
    lv_appicon_delete(appicon);
}
